#include "broadcast_acceptor.hpp"
#include <asio.hpp>
#include <iostream>
#include <utility>
#include <vector>

#include "result.hpp"

class BroadcastAcceptor::Impl
{
public:
    explicit Impl(Callback callback)
        : data_(), callback(std::move(callback))
    {
    }

    cpp::Result<> init(uint16_t listen_port, const std::string& interface_address)
    {
        if (sp_socket != nullptr)
        {
            return cpp::Result<>::Err("Sock is init.");
        }
        asio::ip::udp::endpoint listen_endpoint(
            asio::ip::make_address(interface_address), listen_port);
        sp_socket = std::make_shared<asio::ip::udp::socket>(io_context);
        asio::error_code ec;
        if (sp_socket->open(asio::ip::udp::v4(), ec))
        {
            return cpp::Result<>::Err(ec.message());
        }
        if (sp_socket->set_option(asio::ip::udp::socket::reuse_address(true), ec))
        {
            return cpp::Result<>::Err(ec.message());
        }
        if (sp_socket->bind(listen_endpoint, ec))
        {
            return cpp::Result<>::Err(ec.message());
        }
        do_receive();
        return cpp::Result<>::Ok();
    }

    void run()
    {
        io_context.run();
    }

    cpp::Result<> release()
    {
        if (sp_socket == nullptr)
        {
            return cpp::Result<>::Err("Sock is no init.");
        }
        if (io_context.stopped() == false)
        {
            io_context.stop();
        }
        asio::error_code ec;
        if (sp_socket->close(ec))
        {
            std::cout << ec.message() << std::endl;
            return cpp::Result<>::Err(ec.message());
        }
        sp_socket = nullptr;
        return cpp::Result<>::Ok();
    }

private:
    void do_receive()
    {
        sp_socket->async_receive_from(
            asio::buffer(data_), sender_endpoint_,
            [this](std::error_code ec, std::size_t length)
            {
                if (!ec)
                {
                    std::vector<uint8_t> received_data(data_.begin(), data_.begin() + length);
                    callback(received_data, sender_endpoint_.address().to_string(), sender_endpoint_.port());
                    do_receive();
                }
            });
    }

    asio::io_context io_context;
    std::shared_ptr<asio::ip::udp::socket> sp_socket;
    asio::ip::udp::endpoint sender_endpoint_;
    std::array<char, 1024> data_;
    const Callback callback;
};

BroadcastAcceptor::BroadcastAcceptor(const Callback& callback)
{
    pimpl = std::make_unique<Impl>(callback);
}

BroadcastAcceptor::~BroadcastAcceptor() = default;

cpp::Result<> BroadcastAcceptor::init(uint16_t listen_port, const std::string& interface_address)
{
    return pimpl->init(listen_port, interface_address);
}

void BroadcastAcceptor::run()
{
    pimpl->run();
}

cpp::Result<> BroadcastAcceptor::release()
{
    return pimpl->release();
}
